// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('css', function(config, parserConfig) {
    var inline = parserConfig.inline;
    if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode('text/css');

    var indentUnit = config.indentUnit,
      tokenHooks = parserConfig.tokenHooks,
      documentTypes = parserConfig.documentTypes || {},
      mediaTypes = parserConfig.mediaTypes || {},
      mediaFeatures = parserConfig.mediaFeatures || {},
      mediaValueKeywords = parserConfig.mediaValueKeywords || {},
      propertyKeywords = parserConfig.propertyKeywords || {},
      nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
      fontProperties = parserConfig.fontProperties || {},
      counterDescriptors = parserConfig.counterDescriptors || {},
      colorKeywords = parserConfig.colorKeywords || {},
      valueKeywords = parserConfig.valueKeywords || {},
      allowNested = parserConfig.allowNested,
      lineComment = parserConfig.lineComment,
      supportsAtComponent = parserConfig.supportsAtComponent === true;

    var type, override;
    function ret(style, tp) {
      type = tp;
      return style;
    }

    // Tokenizers

    function tokenBase(stream, state) {
      var ch = stream.next();
      if (tokenHooks[ch]) {
        var result = tokenHooks[ch](stream, state);
        if (result !== false) return result;
      }
      if (ch == '@') {
        stream.eatWhile(/[\w\\\-]/);
        return ret('def', stream.current());
      } else if (ch == '=' || ((ch == '~' || ch == '|') && stream.eat('='))) {
        return ret(null, 'compare');
      } else if (ch == '"' || ch == "'") {
        state.tokenize = tokenString(ch);
        return state.tokenize(stream, state);
      } else if (ch == '#') {
        stream.eatWhile(/[\w\\\-]/);
        return ret('atom', 'hash');
      } else if (ch == '!') {
        stream.match(/^\s*\w*/);
        return ret('keyword', 'important');
      } else if (/\d/.test(ch) || (ch == '.' && stream.eat(/\d/))) {
        stream.eatWhile(/[\w.%]/);
        return ret('number', 'unit');
      } else if (ch === '-') {
        if (/[\d.]/.test(stream.peek())) {
          stream.eatWhile(/[\w.%]/);
          return ret('number', 'unit');
        } else if (stream.match(/^-[\w\\\-]+/)) {
          stream.eatWhile(/[\w\\\-]/);
          if (stream.match(/^\s*:/, false)) return ret('variable-2', 'variable-definition');
          return ret('variable-2', 'variable');
        } else if (stream.match(/^\w+-/)) {
          return ret('meta', 'meta');
        }
      } else if (/[,+>*\/]/.test(ch)) {
        return ret(null, 'select-op');
      } else if (ch == '.' && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
        return ret('qualifier', 'qualifier');
      } else if (/[:;{}\[\]\(\)]/.test(ch)) {
        return ret(null, ch);
      } else if (
        (ch == 'u' && stream.match(/rl(-prefix)?\(/)) ||
        (ch == 'd' && stream.match('omain(')) ||
        (ch == 'r' && stream.match('egexp('))
      ) {
        stream.backUp(1);
        state.tokenize = tokenParenthesized;
        return ret('property', 'word');
      } else if (/[\w\\\-]/.test(ch)) {
        stream.eatWhile(/[\w\\\-]/);
        return ret('property', 'word');
      } else {
        return ret(null, null);
      }
    }

    function tokenString(quote) {
      return function(stream, state) {
        var escaped = false,
          ch;
        while ((ch = stream.next()) != null) {
          if (ch == quote && !escaped) {
            if (quote == ')') stream.backUp(1);
            break;
          }
          escaped = !escaped && ch == '\\';
        }
        if (ch == quote || (!escaped && quote != ')')) state.tokenize = null;
        return ret('string', 'string');
      };
    }

    function tokenParenthesized(stream, state) {
      stream.next(); // Must be '('
      if (!stream.match(/\s*[\"\')]/, false)) state.tokenize = tokenString(')');
      else state.tokenize = null;
      return ret(null, '(');
    }

    // Context management

    function Context(type, indent, prev) {
      this.type = type;
      this.indent = indent;
      this.prev = prev;
    }

    function pushContext(state, stream, type, indent) {
      state.context = new Context(
        type,
        stream.indentation() + (indent === false ? 0 : indentUnit),
        state.context,
      );
      return type;
    }

    function popContext(state) {
      if (state.context.prev) state.context = state.context.prev;
      return state.context.type;
    }

    function pass(type, stream, state) {
      return states[state.context.type](type, stream, state);
    }
    function popAndPass(type, stream, state, n) {
      for (var i = n || 1; i > 0; i--) state.context = state.context.prev;
      return pass(type, stream, state);
    }

    // Parser

    function wordAsValue(stream) {
      var word = stream.current().toLowerCase();
      if (valueKeywords.hasOwnProperty(word)) override = 'atom';
      else if (colorKeywords.hasOwnProperty(word)) override = 'keyword';
      else override = 'variable';
    }

    var states = {};

    states.top = function(type, stream, state) {
      if (type == '{') {
        return pushContext(state, stream, 'block');
      } else if (type == '}' && state.context.prev) {
        return popContext(state);
      } else if (supportsAtComponent && /@component/.test(type)) {
        return pushContext(state, stream, 'atComponentBlock');
      } else if (/^@(-moz-)?document$/.test(type)) {
        return pushContext(state, stream, 'documentTypes');
      } else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
        return pushContext(state, stream, 'atBlock');
      } else if (/^@(font-face|counter-style)/.test(type)) {
        state.stateArg = type;
        return 'restricted_atBlock_before';
      } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
        return 'keyframes';
      } else if (type && type.charAt(0) == '@') {
        return pushContext(state, stream, 'at');
      } else if (type == 'hash') {
        override = 'builtin';
      } else if (type == 'word') {
        override = 'tag';
      } else if (type == 'variable-definition') {
        return 'maybeprop';
      } else if (type == 'interpolation') {
        return pushContext(state, stream, 'interpolation');
      } else if (type == ':') {
        return 'pseudo';
      } else if (allowNested && type == '(') {
        return pushContext(state, stream, 'parens');
      }
      return state.context.type;
    };

    states.block = function(type, stream, state) {
      if (type == 'word') {
        var word = stream.current().toLowerCase();
        if (propertyKeywords.hasOwnProperty(word)) {
          override = 'property';
          return 'maybeprop';
        } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
          override = 'string-2';
          return 'maybeprop';
        } else if (allowNested) {
          override = stream.match(/^\s*:(?:\s|$)/, false) ? 'property' : 'tag';
          return 'block';
        } else {
          override += ' error';
          return 'maybeprop';
        }
      } else if (type == 'meta') {
        return 'block';
      } else if (!allowNested && (type == 'hash' || type == 'qualifier')) {
        override = 'error';
        return 'block';
      } else {
        return states.top(type, stream, state);
      }
    };

    states.maybeprop = function(type, stream, state) {
      if (type == ':') return pushContext(state, stream, 'prop');
      return pass(type, stream, state);
    };

    states.prop = function(type, stream, state) {
      if (type == ';') return popContext(state);
      if (type == '{' && allowNested) return pushContext(state, stream, 'propBlock');
      if (type == '}' || type == '{') return popAndPass(type, stream, state);
      if (type == '(') return pushContext(state, stream, 'parens');

      if (
        type == 'hash' &&
        !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())
      ) {
        override += ' error';
      } else if (type == 'word') {
        wordAsValue(stream);
      } else if (type == 'interpolation') {
        return pushContext(state, stream, 'interpolation');
      }
      return 'prop';
    };

    states.propBlock = function(type, _stream, state) {
      if (type == '}') return popContext(state);
      if (type == 'word') {
        override = 'property';
        return 'maybeprop';
      }
      return state.context.type;
    };

    states.parens = function(type, stream, state) {
      if (type == '{' || type == '}') return popAndPass(type, stream, state);
      if (type == ')') return popContext(state);
      if (type == '(') return pushContext(state, stream, 'parens');
      if (type == 'interpolation') return pushContext(state, stream, 'interpolation');
      if (type == 'word') wordAsValue(stream);
      return 'parens';
    };

    states.pseudo = function(type, stream, state) {
      if (type == 'meta') return 'pseudo';

      if (type == 'word') {
        override = 'variable-3';
        return state.context.type;
      }
      return pass(type, stream, state);
    };

    states.documentTypes = function(type, stream, state) {
      if (type == 'word' && documentTypes.hasOwnProperty(stream.current())) {
        override = 'tag';
        return state.context.type;
      } else {
        return states.atBlock(type, stream, state);
      }
    };

    states.atBlock = function(type, stream, state) {
      if (type == '(') return pushContext(state, stream, 'atBlock_parens');
      if (type == '}' || type == ';') return popAndPass(type, stream, state);
      if (type == '{')
        return popContext(state) && pushContext(state, stream, allowNested ? 'block' : 'top');

      if (type == 'interpolation') return pushContext(state, stream, 'interpolation');

      if (type == 'word') {
        var word = stream.current().toLowerCase();
        if (word == 'only' || word == 'not' || word == 'and' || word == 'or') override = 'keyword';
        else if (mediaTypes.hasOwnProperty(word)) override = 'attribute';
        else if (mediaFeatures.hasOwnProperty(word)) override = 'property';
        else if (mediaValueKeywords.hasOwnProperty(word)) override = 'keyword';
        else if (propertyKeywords.hasOwnProperty(word)) override = 'property';
        else if (nonStandardPropertyKeywords.hasOwnProperty(word)) override = 'string-2';
        else if (valueKeywords.hasOwnProperty(word)) override = 'atom';
        else if (colorKeywords.hasOwnProperty(word)) override = 'keyword';
        else override = 'error';
      }
      return state.context.type;
    };

    states.atComponentBlock = function(type, stream, state) {
      if (type == '}') return popAndPass(type, stream, state);
      if (type == '{')
        return (
          popContext(state) && pushContext(state, stream, allowNested ? 'block' : 'top', false)
        );
      if (type == 'word') override = 'error';
      return state.context.type;
    };

    states.atBlock_parens = function(type, stream, state) {
      if (type == ')') return popContext(state);
      if (type == '{' || type == '}') return popAndPass(type, stream, state, 2);
      return states.atBlock(type, stream, state);
    };

    states.restricted_atBlock_before = function(type, stream, state) {
      if (type == '{') return pushContext(state, stream, 'restricted_atBlock');
      if (type == 'word' && state.stateArg == '@counter-style') {
        override = 'variable';
        return 'restricted_atBlock_before';
      }
      return pass(type, stream, state);
    };

    states.restricted_atBlock = function(type, stream, state) {
      if (type == '}') {
        state.stateArg = null;
        return popContext(state);
      }
      if (type == 'word') {
        if (
          (state.stateArg == '@font-face' &&
            !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
          (state.stateArg == '@counter-style' &&
            !counterDescriptors.hasOwnProperty(stream.current().toLowerCase()))
        )
          override = 'error';
        else override = 'property';
        return 'maybeprop';
      }
      return 'restricted_atBlock';
    };

    states.keyframes = function(type, stream, state) {
      if (type == 'word') {
        override = 'variable';
        return 'keyframes';
      }
      if (type == '{') return pushContext(state, stream, 'top');
      return pass(type, stream, state);
    };

    states.at = function(type, stream, state) {
      if (type == ';') return popContext(state);
      if (type == '{' || type == '}') return popAndPass(type, stream, state);
      if (type == 'word') override = 'tag';
      else if (type == 'hash') override = 'builtin';
      return 'at';
    };

    states.interpolation = function(type, stream, state) {
      if (type == '}') return popContext(state);
      if (type == '{' || type == ';') return popAndPass(type, stream, state);
      if (type == 'word') override = 'variable';
      else if (type != 'variable' && type != '(' && type != ')') override = 'error';
      return 'interpolation';
    };

    return {
      startState: function(base) {
        return {
          tokenize: null,
          state: inline ? 'block' : 'top',
          stateArg: null,
          context: new Context(inline ? 'block' : 'top', base || 0, null),
        };
      },

      token: function(stream, state) {
        if (!state.tokenize && stream.eatSpace()) return null;
        var style = (state.tokenize || tokenBase)(stream, state);
        if (style && typeof style == 'object') {
          type = style[1];
          style = style[0];
        }
        override = style;
        state.state = states[state.state](type, stream, state);
        return override;
      },

      indent: function(state, textAfter) {
        var cx = state.context,
          ch = textAfter && textAfter.charAt(0);
        var indent = cx.indent;
        if (cx.type == 'prop' && (ch == '}' || ch == ')')) cx = cx.prev;
        if (cx.prev) {
          if (
            ch == '}' &&
            (cx.type == 'block' ||
              cx.type == 'top' ||
              cx.type == 'interpolation' ||
              cx.type == 'restricted_atBlock')
          ) {
            // Resume indentation from parent context.
            cx = cx.prev;
            indent = cx.indent;
          } else if (
            (ch == ')' && (cx.type == 'parens' || cx.type == 'atBlock_parens')) ||
            (ch == '{' && (cx.type == 'at' || cx.type == 'atBlock'))
          ) {
            // Dedent relative to current context.
            indent = Math.max(0, cx.indent - indentUnit);
          }
        }
        return indent;
      },

      electricChars: '}',
      blockCommentStart: '/*',
      blockCommentEnd: '*/',
      lineComment: lineComment,
      fold: 'brace',
    };
  });

  function keySet(array) {
    var keys = {};
    for (var i = 0; i < array.length; ++i) {
      keys[array[i].toLowerCase()] = true;
    }
    return keys;
  }

  var documentTypes_ = ['domain', 'regexp', 'url', 'url-prefix'],
    documentTypes = keySet(documentTypes_);

  var mediaTypes_ = [
      'all',
      'aural',
      'braille',
      'handheld',
      'print',
      'projection',
      'screen',
      'tty',
      'tv',
      'embossed',
    ],
    mediaTypes = keySet(mediaTypes_);

  var mediaFeatures_ = [
      'width',
      'min-width',
      'max-width',
      'height',
      'min-height',
      'max-height',
      'device-width',
      'min-device-width',
      'max-device-width',
      'device-height',
      'min-device-height',
      'max-device-height',
      'aspect-ratio',
      'min-aspect-ratio',
      'max-aspect-ratio',
      'device-aspect-ratio',
      'min-device-aspect-ratio',
      'max-device-aspect-ratio',
      'color',
      'min-color',
      'max-color',
      'color-index',
      'min-color-index',
      'max-color-index',
      'monochrome',
      'min-monochrome',
      'max-monochrome',
      'resolution',
      'min-resolution',
      'max-resolution',
      'scan',
      'grid',
      'orientation',
      'device-pixel-ratio',
      'min-device-pixel-ratio',
      'max-device-pixel-ratio',
      'pointer',
      'any-pointer',
      'hover',
      'any-hover',
    ],
    mediaFeatures = keySet(mediaFeatures_);

  var mediaValueKeywords_ = [
      'landscape',
      'portrait',
      'none',
      'coarse',
      'fine',
      'on-demand',
      'hover',
      'interlace',
      'progressive',
    ],
    mediaValueKeywords = keySet(mediaValueKeywords_);

  var propertyKeywords_ = [
      'align-content',
      'align-items',
      'align-self',
      'alignment-adjust',
      'alignment-baseline',
      'anchor-point',
      'animation',
      'animation-delay',
      'animation-direction',
      'animation-duration',
      'animation-fill-mode',
      'animation-iteration-count',
      'animation-name',
      'animation-play-state',
      'animation-timing-function',
      'appearance',
      'azimuth',
      'backface-visibility',
      'background',
      'background-attachment',
      'background-blend-mode',
      'background-clip',
      'background-color',
      'background-image',
      'background-origin',
      'background-position',
      'background-repeat',
      'background-size',
      'baseline-shift',
      'binding',
      'bleed',
      'bookmark-label',
      'bookmark-level',
      'bookmark-state',
      'bookmark-target',
      'border',
      'border-bottom',
      'border-bottom-color',
      'border-bottom-left-radius',
      'border-bottom-right-radius',
      'border-bottom-style',
      'border-bottom-width',
      'border-collapse',
      'border-color',
      'border-image',
      'border-image-outset',
      'border-image-repeat',
      'border-image-slice',
      'border-image-source',
      'border-image-width',
      'border-left',
      'border-left-color',
      'border-left-style',
      'border-left-width',
      'border-radius',
      'border-right',
      'border-right-color',
      'border-right-style',
      'border-right-width',
      'border-spacing',
      'border-style',
      'border-top',
      'border-top-color',
      'border-top-left-radius',
      'border-top-right-radius',
      'border-top-style',
      'border-top-width',
      'border-width',
      'bottom',
      'box-decoration-break',
      'box-shadow',
      'box-sizing',
      'break-after',
      'break-before',
      'break-inside',
      'caption-side',
      'caret-color',
      'clear',
      'clip',
      'color',
      'color-profile',
      'column-count',
      'column-fill',
      'column-gap',
      'column-rule',
      'column-rule-color',
      'column-rule-style',
      'column-rule-width',
      'column-span',
      'column-width',
      'columns',
      'content',
      'counter-increment',
      'counter-reset',
      'crop',
      'cue',
      'cue-after',
      'cue-before',
      'cursor',
      'direction',
      'display',
      'dominant-baseline',
      'drop-initial-after-adjust',
      'drop-initial-after-align',
      'drop-initial-before-adjust',
      'drop-initial-before-align',
      'drop-initial-size',
      'drop-initial-value',
      'elevation',
      'empty-cells',
      'fit',
      'fit-position',
      'flex',
      'flex-basis',
      'flex-direction',
      'flex-flow',
      'flex-grow',
      'flex-shrink',
      'flex-wrap',
      'float',
      'float-offset',
      'flow-from',
      'flow-into',
      'font',
      'font-feature-settings',
      'font-family',
      'font-kerning',
      'font-language-override',
      'font-size',
      'font-size-adjust',
      'font-stretch',
      'font-style',
      'font-synthesis',
      'font-variant',
      'font-variant-alternates',
      'font-variant-caps',
      'font-variant-east-asian',
      'font-variant-ligatures',
      'font-variant-numeric',
      'font-variant-position',
      'font-weight',
      'grid',
      'grid-area',
      'grid-auto-columns',
      'grid-auto-flow',
      'grid-auto-rows',
      'grid-column',
      'grid-column-end',
      'grid-column-gap',
      'grid-column-start',
      'grid-gap',
      'grid-row',
      'grid-row-end',
      'grid-row-gap',
      'grid-row-start',
      'grid-template',
      'grid-template-areas',
      'grid-template-columns',
      'grid-template-rows',
      'hanging-punctuation',
      'height',
      'hyphens',
      'icon',
      'image-orientation',
      'image-rendering',
      'image-resolution',
      'inline-box-align',
      'justify-content',
      'justify-items',
      'justify-self',
      'left',
      'letter-spacing',
      'line-break',
      'line-height',
      'line-stacking',
      'line-stacking-ruby',
      'line-stacking-shift',
      'line-stacking-strategy',
      'list-style',
      'list-style-image',
      'list-style-position',
      'list-style-type',
      'margin',
      'margin-bottom',
      'margin-left',
      'margin-right',
      'margin-top',
      'marks',
      'marquee-direction',
      'marquee-loop',
      'marquee-play-count',
      'marquee-speed',
      'marquee-style',
      'max-height',
      'max-width',
      'min-height',
      'min-width',
      'move-to',
      'nav-down',
      'nav-index',
      'nav-left',
      'nav-right',
      'nav-up',
      'object-fit',
      'object-position',
      'opacity',
      'order',
      'orphans',
      'outline',
      'outline-color',
      'outline-offset',
      'outline-style',
      'outline-width',
      'overflow',
      'overflow-style',
      'overflow-wrap',
      'overflow-x',
      'overflow-y',
      'padding',
      'padding-bottom',
      'padding-left',
      'padding-right',
      'padding-top',
      'page',
      'page-break-after',
      'page-break-before',
      'page-break-inside',
      'page-policy',
      'pause',
      'pause-after',
      'pause-before',
      'perspective',
      'perspective-origin',
      'pitch',
      'pitch-range',
      'place-content',
      'place-items',
      'place-self',
      'play-during',
      'position',
      'presentation-level',
      'punctuation-trim',
      'quotes',
      'region-break-after',
      'region-break-before',
      'region-break-inside',
      'region-fragment',
      'rendering-intent',
      'resize',
      'rest',
      'rest-after',
      'rest-before',
      'richness',
      'right',
      'rotation',
      'rotation-point',
      'ruby-align',
      'ruby-overhang',
      'ruby-position',
      'ruby-span',
      'shape-image-threshold',
      'shape-inside',
      'shape-margin',
      'shape-outside',
      'size',
      'speak',
      'speak-as',
      'speak-header',
      'speak-numeral',
      'speak-punctuation',
      'speech-rate',
      'stress',
      'string-set',
      'tab-size',
      'table-layout',
      'target',
      'target-name',
      'target-new',
      'target-position',
      'text-align',
      'text-align-last',
      'text-decoration',
      'text-decoration-color',
      'text-decoration-line',
      'text-decoration-skip',
      'text-decoration-style',
      'text-emphasis',
      'text-emphasis-color',
      'text-emphasis-position',
      'text-emphasis-style',
      'text-height',
      'text-indent',
      'text-justify',
      'text-outline',
      'text-overflow',
      'text-shadow',
      'text-size-adjust',
      'text-space-collapse',
      'text-transform',
      'text-underline-position',
      'text-wrap',
      'top',
      'transform',
      'transform-origin',
      'transform-style',
      'transition',
      'transition-delay',
      'transition-duration',
      'transition-property',
      'transition-timing-function',
      'unicode-bidi',
      'user-select',
      'vertical-align',
      'visibility',
      'voice-balance',
      'voice-duration',
      'voice-family',
      'voice-pitch',
      'voice-range',
      'voice-rate',
      'voice-stress',
      'voice-volume',
      'volume',
      'white-space',
      'widows',
      'width',
      'will-change',
      'word-break',
      'word-spacing',
      'word-wrap',
      'z-index',
      // SVG-specific
      'clip-path',
      'clip-rule',
      'mask',
      'enable-background',
      'filter',
      'flood-color',
      'flood-opacity',
      'lighting-color',
      'stop-color',
      'stop-opacity',
      'pointer-events',
      'color-interpolation',
      'color-interpolation-filters',
      'color-rendering',
      'fill',
      'fill-opacity',
      'fill-rule',
      'image-rendering',
      'marker',
      'marker-end',
      'marker-mid',
      'marker-start',
      'shape-rendering',
      'stroke',
      'stroke-dasharray',
      'stroke-dashoffset',
      'stroke-linecap',
      'stroke-linejoin',
      'stroke-miterlimit',
      'stroke-opacity',
      'stroke-width',
      'text-rendering',
      'baseline-shift',
      'dominant-baseline',
      'glyph-orientation-horizontal',
      'glyph-orientation-vertical',
      'text-anchor',
      'writing-mode',
    ],
    propertyKeywords = keySet(propertyKeywords_);

  var nonStandardPropertyKeywords_ = [
      'scrollbar-arrow-color',
      'scrollbar-base-color',
      'scrollbar-dark-shadow-color',
      'scrollbar-face-color',
      'scrollbar-highlight-color',
      'scrollbar-shadow-color',
      'scrollbar-3d-light-color',
      'scrollbar-track-color',
      'shape-inside',
      'searchfield-cancel-button',
      'searchfield-decoration',
      'searchfield-results-button',
      'searchfield-results-decoration',
      'zoom',
    ],
    nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);

  var fontProperties_ = [
      'font-family',
      'src',
      'unicode-range',
      'font-variant',
      'font-feature-settings',
      'font-stretch',
      'font-weight',
      'font-style',
    ],
    fontProperties = keySet(fontProperties_);

  var counterDescriptors_ = [
      'additive-symbols',
      'fallback',
      'negative',
      'pad',
      'prefix',
      'range',
      'speak-as',
      'suffix',
      'symbols',
      'system',
    ],
    counterDescriptors = keySet(counterDescriptors_);

  var colorKeywords_ = [
      'aliceblue',
      'antiquewhite',
      'aqua',
      'aquamarine',
      'azure',
      'beige',
      'bisque',
      'black',
      'blanchedalmond',
      'blue',
      'blueviolet',
      'brown',
      'burlywood',
      'cadetblue',
      'chartreuse',
      'chocolate',
      'coral',
      'cornflowerblue',
      'cornsilk',
      'crimson',
      'cyan',
      'darkblue',
      'darkcyan',
      'darkgoldenrod',
      'darkgray',
      'darkgreen',
      'darkkhaki',
      'darkmagenta',
      'darkolivegreen',
      'darkorange',
      'darkorchid',
      'darkred',
      'darksalmon',
      'darkseagreen',
      'darkslateblue',
      'darkslategray',
      'darkturquoise',
      'darkviolet',
      'deeppink',
      'deepskyblue',
      'dimgray',
      'dodgerblue',
      'firebrick',
      'floralwhite',
      'forestgreen',
      'fuchsia',
      'gainsboro',
      'ghostwhite',
      'gold',
      'goldenrod',
      'gray',
      'grey',
      'green',
      'greenyellow',
      'honeydew',
      'hotpink',
      'indianred',
      'indigo',
      'ivory',
      'khaki',
      'lavender',
      'lavenderblush',
      'lawngreen',
      'lemonchiffon',
      'lightblue',
      'lightcoral',
      'lightcyan',
      'lightgoldenrodyellow',
      'lightgray',
      'lightgreen',
      'lightpink',
      'lightsalmon',
      'lightseagreen',
      'lightskyblue',
      'lightslategray',
      'lightsteelblue',
      'lightyellow',
      'lime',
      'limegreen',
      'linen',
      'magenta',
      'maroon',
      'mediumaquamarine',
      'mediumblue',
      'mediumorchid',
      'mediumpurple',
      'mediumseagreen',
      'mediumslateblue',
      'mediumspringgreen',
      'mediumturquoise',
      'mediumvioletred',
      'midnightblue',
      'mintcream',
      'mistyrose',
      'moccasin',
      'navajowhite',
      'navy',
      'oldlace',
      'olive',
      'olivedrab',
      'orange',
      'orangered',
      'orchid',
      'palegoldenrod',
      'palegreen',
      'paleturquoise',
      'palevioletred',
      'papayawhip',
      'peachpuff',
      'peru',
      'pink',
      'plum',
      'powderblue',
      'purple',
      'rebeccapurple',
      'red',
      'rosybrown',
      'royalblue',
      'saddlebrown',
      'salmon',
      'sandybrown',
      'seagreen',
      'seashell',
      'sienna',
      'silver',
      'skyblue',
      'slateblue',
      'slategray',
      'snow',
      'springgreen',
      'steelblue',
      'tan',
      'teal',
      'thistle',
      'tomato',
      'turquoise',
      'violet',
      'wheat',
      'white',
      'whitesmoke',
      'yellow',
      'yellowgreen',
    ],
    colorKeywords = keySet(colorKeywords_);

  var valueKeywords_ = [
      'above',
      'absolute',
      'activeborder',
      'additive',
      'activecaption',
      'afar',
      'after-white-space',
      'ahead',
      'alias',
      'all',
      'all-scroll',
      'alphabetic',
      'alternate',
      'always',
      'amharic',
      'amharic-abegede',
      'antialiased',
      'appworkspace',
      'arabic-indic',
      'armenian',
      'asterisks',
      'attr',
      'auto',
      'auto-flow',
      'avoid',
      'avoid-column',
      'avoid-page',
      'avoid-region',
      'background',
      'backwards',
      'baseline',
      'below',
      'bidi-override',
      'binary',
      'bengali',
      'blink',
      'block',
      'block-axis',
      'bold',
      'bolder',
      'border',
      'border-box',
      'both',
      'bottom',
      'break',
      'break-all',
      'break-word',
      'bullets',
      'button',
      'button-bevel',
      'buttonface',
      'buttonhighlight',
      'buttonshadow',
      'buttontext',
      'calc',
      'cambodian',
      'capitalize',
      'caps-lock-indicator',
      'caption',
      'captiontext',
      'caret',
      'cell',
      'center',
      'checkbox',
      'circle',
      'cjk-decimal',
      'cjk-earthly-branch',
      'cjk-heavenly-stem',
      'cjk-ideographic',
      'clear',
      'clip',
      'close-quote',
      'col-resize',
      'collapse',
      'color',
      'color-burn',
      'color-dodge',
      'column',
      'column-reverse',
      'compact',
      'condensed',
      'contain',
      'content',
      'contents',
      'content-box',
      'context-menu',
      'continuous',
      'copy',
      'counter',
      'counters',
      'cover',
      'crop',
      'cross',
      'crosshair',
      'currentcolor',
      'cursive',
      'cyclic',
      'darken',
      'dashed',
      'decimal',
      'decimal-leading-zero',
      'default',
      'default-button',
      'dense',
      'destination-atop',
      'destination-in',
      'destination-out',
      'destination-over',
      'devanagari',
      'difference',
      'disc',
      'discard',
      'disclosure-closed',
      'disclosure-open',
      'document',
      'dot-dash',
      'dot-dot-dash',
      'dotted',
      'double',
      'down',
      'e-resize',
      'ease',
      'ease-in',
      'ease-in-out',
      'ease-out',
      'element',
      'ellipse',
      'ellipsis',
      'embed',
      'end',
      'ethiopic',
      'ethiopic-abegede',
      'ethiopic-abegede-am-et',
      'ethiopic-abegede-gez',
      'ethiopic-abegede-ti-er',
      'ethiopic-abegede-ti-et',
      'ethiopic-halehame-aa-er',
      'ethiopic-halehame-aa-et',
      'ethiopic-halehame-am-et',
      'ethiopic-halehame-gez',
      'ethiopic-halehame-om-et',
      'ethiopic-halehame-sid-et',
      'ethiopic-halehame-so-et',
      'ethiopic-halehame-ti-er',
      'ethiopic-halehame-ti-et',
      'ethiopic-halehame-tig',
      'ethiopic-numeric',
      'ew-resize',
      'exclusion',
      'expanded',
      'extends',
      'extra-condensed',
      'extra-expanded',
      'fantasy',
      'fast',
      'fill',
      'fixed',
      'flat',
      'flex',
      'flex-end',
      'flex-start',
      'footnotes',
      'forwards',
      'from',
      'geometricPrecision',
      'georgian',
      'graytext',
      'grid',
      'groove',
      'gujarati',
      'gurmukhi',
      'hand',
      'hangul',
      'hangul-consonant',
      'hard-light',
      'hebrew',
      'help',
      'hidden',
      'hide',
      'higher',
      'highlight',
      'highlighttext',
      'hiragana',
      'hiragana-iroha',
      'horizontal',
      'hsl',
      'hsla',
      'hue',
      'icon',
      'ignore',
      'inactiveborder',
      'inactivecaption',
      'inactivecaptiontext',
      'infinite',
      'infobackground',
      'infotext',
      'inherit',
      'initial',
      'inline',
      'inline-axis',
      'inline-block',
      'inline-flex',
      'inline-grid',
      'inline-table',
      'inset',
      'inside',
      'intrinsic',
      'invert',
      'italic',
      'japanese-formal',
      'japanese-informal',
      'justify',
      'kannada',
      'katakana',
      'katakana-iroha',
      'keep-all',
      'khmer',
      'korean-hangul-formal',
      'korean-hanja-formal',
      'korean-hanja-informal',
      'landscape',
      'lao',
      'large',
      'larger',
      'left',
      'level',
      'lighter',
      'lighten',
      'line-through',
      'linear',
      'linear-gradient',
      'lines',
      'list-item',
      'listbox',
      'listitem',
      'local',
      'logical',
      'loud',
      'lower',
      'lower-alpha',
      'lower-armenian',
      'lower-greek',
      'lower-hexadecimal',
      'lower-latin',
      'lower-norwegian',
      'lower-roman',
      'lowercase',
      'ltr',
      'luminosity',
      'malayalam',
      'match',
      'matrix',
      'matrix3d',
      'media-controls-background',
      'media-current-time-display',
      'media-fullscreen-button',
      'media-mute-button',
      'media-play-button',
      'media-return-to-realtime-button',
      'media-rewind-button',
      'media-seek-back-button',
      'media-seek-forward-button',
      'media-slider',
      'media-sliderthumb',
      'media-time-remaining-display',
      'media-volume-slider',
      'media-volume-slider-container',
      'media-volume-sliderthumb',
      'medium',
      'menu',
      'menulist',
      'menulist-button',
      'menulist-text',
      'menulist-textfield',
      'menutext',
      'message-box',
      'middle',
      'min-intrinsic',
      'mix',
      'mongolian',
      'monospace',
      'move',
      'multiple',
      'multiply',
      'myanmar',
      'n-resize',
      'narrower',
      'ne-resize',
      'nesw-resize',
      'no-close-quote',
      'no-drop',
      'no-open-quote',
      'no-repeat',
      'none',
      'normal',
      'not-allowed',
      'nowrap',
      'ns-resize',
      'numbers',
      'numeric',
      'nw-resize',
      'nwse-resize',
      'oblique',
      'octal',
      'opacity',
      'open-quote',
      'optimizeLegibility',
      'optimizeSpeed',
      'oriya',
      'oromo',
      'outset',
      'outside',
      'outside-shape',
      'overlay',
      'overline',
      'padding',
      'padding-box',
      'painted',
      'page',
      'paused',
      'persian',
      'perspective',
      'plus-darker',
      'plus-lighter',
      'pointer',
      'polygon',
      'portrait',
      'pre',
      'pre-line',
      'pre-wrap',
      'preserve-3d',
      'progress',
      'push-button',
      'radial-gradient',
      'radio',
      'read-only',
      'read-write',
      'read-write-plaintext-only',
      'rectangle',
      'region',
      'relative',
      'repeat',
      'repeating-linear-gradient',
      'repeating-radial-gradient',
      'repeat-x',
      'repeat-y',
      'reset',
      'reverse',
      'rgb',
      'rgba',
      'ridge',
      'right',
      'rotate',
      'rotate3d',
      'rotateX',
      'rotateY',
      'rotateZ',
      'round',
      'row',
      'row-resize',
      'row-reverse',
      'rtl',
      'run-in',
      'running',
      's-resize',
      'sans-serif',
      'saturation',
      'scale',
      'scale3d',
      'scaleX',
      'scaleY',
      'scaleZ',
      'screen',
      'scroll',
      'scrollbar',
      'scroll-position',
      'se-resize',
      'searchfield',
      'searchfield-cancel-button',
      'searchfield-decoration',
      'searchfield-results-button',
      'searchfield-results-decoration',
      'self-start',
      'self-end',
      'semi-condensed',
      'semi-expanded',
      'separate',
      'serif',
      'show',
      'sidama',
      'simp-chinese-formal',
      'simp-chinese-informal',
      'single',
      'skew',
      'skewX',
      'skewY',
      'skip-white-space',
      'slide',
      'slider-horizontal',
      'slider-vertical',
      'sliderthumb-horizontal',
      'sliderthumb-vertical',
      'slow',
      'small',
      'small-caps',
      'small-caption',
      'smaller',
      'soft-light',
      'solid',
      'somali',
      'source-atop',
      'source-in',
      'source-out',
      'source-over',
      'space',
      'space-around',
      'space-between',
      'space-evenly',
      'spell-out',
      'square',
      'square-button',
      'start',
      'static',
      'status-bar',
      'stretch',
      'stroke',
      'sub',
      'subpixel-antialiased',
      'super',
      'sw-resize',
      'symbolic',
      'symbols',
      'system-ui',
      'table',
      'table-caption',
      'table-cell',
      'table-column',
      'table-column-group',
      'table-footer-group',
      'table-header-group',
      'table-row',
      'table-row-group',
      'tamil',
      'telugu',
      'text',
      'text-bottom',
      'text-top',
      'textarea',
      'textfield',
      'thai',
      'thick',
      'thin',
      'threeddarkshadow',
      'threedface',
      'threedhighlight',
      'threedlightshadow',
      'threedshadow',
      'tibetan',
      'tigre',
      'tigrinya-er',
      'tigrinya-er-abegede',
      'tigrinya-et',
      'tigrinya-et-abegede',
      'to',
      'top',
      'trad-chinese-formal',
      'trad-chinese-informal',
      'transform',
      'translate',
      'translate3d',
      'translateX',
      'translateY',
      'translateZ',
      'transparent',
      'ultra-condensed',
      'ultra-expanded',
      'underline',
      'unset',
      'up',
      'upper-alpha',
      'upper-armenian',
      'upper-greek',
      'upper-hexadecimal',
      'upper-latin',
      'upper-norwegian',
      'upper-roman',
      'uppercase',
      'urdu',
      'url',
      'var',
      'vertical',
      'vertical-text',
      'visible',
      'visibleFill',
      'visiblePainted',
      'visibleStroke',
      'visual',
      'w-resize',
      'wait',
      'wave',
      'wider',
      'window',
      'windowframe',
      'windowtext',
      'words',
      'wrap',
      'wrap-reverse',
      'x-large',
      'x-small',
      'xor',
      'xx-large',
      'xx-small',
    ],
    valueKeywords = keySet(valueKeywords_);

  var allWords = documentTypes_
    .concat(mediaTypes_)
    .concat(mediaFeatures_)
    .concat(mediaValueKeywords_)
    .concat(propertyKeywords_)
    .concat(nonStandardPropertyKeywords_)
    .concat(colorKeywords_)
    .concat(valueKeywords_);
  CodeMirror.registerHelper('hintWords', 'css', allWords);

  function tokenCComment(stream, state) {
    var maybeEnd = false,
      ch;
    while ((ch = stream.next()) != null) {
      if (maybeEnd && ch == '/') {
        state.tokenize = null;
        break;
      }
      maybeEnd = ch == '*';
    }
    return ['comment', 'comment'];
  }

  CodeMirror.defineMIME('text/css', {
    documentTypes: documentTypes,
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    mediaValueKeywords: mediaValueKeywords,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    fontProperties: fontProperties,
    counterDescriptors: counterDescriptors,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    tokenHooks: {
      '/': function(stream, state) {
        if (!stream.eat('*')) return false;
        state.tokenize = tokenCComment;
        return tokenCComment(stream, state);
      },
    },
    name: 'css',
  });

  CodeMirror.defineMIME('text/x-scss', {
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    mediaValueKeywords: mediaValueKeywords,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    fontProperties: fontProperties,
    allowNested: true,
    lineComment: '//',
    tokenHooks: {
      '/': function(stream, state) {
        if (stream.eat('/')) {
          stream.skipToEnd();
          return ['comment', 'comment'];
        } else if (stream.eat('*')) {
          state.tokenize = tokenCComment;
          return tokenCComment(stream, state);
        } else {
          return ['operator', 'operator'];
        }
      },
      ':': function(stream) {
        if (stream.match(/\s*\{/, false)) return [null, null];
        return false;
      },
      $: function(stream) {
        stream.match(/^[\w-]+/);
        if (stream.match(/^\s*:/, false)) return ['variable-2', 'variable-definition'];
        return ['variable-2', 'variable'];
      },
      '#': function(stream) {
        if (!stream.eat('{')) return false;
        return [null, 'interpolation'];
      },
    },
    name: 'css',
    helperType: 'scss',
  });

  CodeMirror.defineMIME('text/x-less', {
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    mediaValueKeywords: mediaValueKeywords,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    fontProperties: fontProperties,
    allowNested: true,
    lineComment: '//',
    tokenHooks: {
      '/': function(stream, state) {
        if (stream.eat('/')) {
          stream.skipToEnd();
          return ['comment', 'comment'];
        } else if (stream.eat('*')) {
          state.tokenize = tokenCComment;
          return tokenCComment(stream, state);
        } else {
          return ['operator', 'operator'];
        }
      },
      '@': function(stream) {
        if (stream.eat('{')) return [null, 'interpolation'];
        if (
          stream.match(
            /^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/,
            false,
          )
        )
          return false;
        stream.eatWhile(/[\w\\\-]/);
        if (stream.match(/^\s*:/, false)) return ['variable-2', 'variable-definition'];
        return ['variable-2', 'variable'];
      },
      '&': function() {
        return ['atom', 'atom'];
      },
    },
    name: 'css',
    helperType: 'less',
  });

  CodeMirror.defineMIME('text/x-gss', {
    documentTypes: documentTypes,
    mediaTypes: mediaTypes,
    mediaFeatures: mediaFeatures,
    propertyKeywords: propertyKeywords,
    nonStandardPropertyKeywords: nonStandardPropertyKeywords,
    fontProperties: fontProperties,
    counterDescriptors: counterDescriptors,
    colorKeywords: colorKeywords,
    valueKeywords: valueKeywords,
    supportsAtComponent: true,
    tokenHooks: {
      '/': function(stream, state) {
        if (!stream.eat('*')) return false;
        state.tokenize = tokenCComment;
        return tokenCComment(stream, state);
      },
    },
    name: 'css',
    helperType: 'gss',
  });
});
